
package fileby.model.impl;

import fileby.model.impl.sources.FileItem;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import java.awt.datatransfer.UnsupportedFlavorException;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.TransferHandler;

import fileby.util.IObservable;
import fileby.core.IDisplayableFileSource;
import fileby.core.UpdateEvent;
import fileby.model.IModel;
import fileby.model.impl.filemanagement.IFileHandler;
import fileby.model.impl.sources.IFileSource;
import fileby.model.impl.sources.IFileSourceManager;
import fileby.core.SourceType;
import fileby.model.IClipboardManager;
import fileby.model.IFavoriteManager;
import fileby.model.IFileManager;
import fileby.model.IMagicBoxManager;
import fileby.model.ISelectionManager;
import fileby.model.ISourceHandler;
import fileby.model.impl.sources.impl.FavoriteSource;
import fileby.plugins.IToolbox;
import fileby.util.plugins.InvalidPluginException;
import fileby.model.impl.sources.IFileSourceProvider;
import fileby.model.impl.sources.impl.MagicBoxManager;

/**
 *
 * @author Daniel
 *
 * This is the central class in the model package which delegates to all
 * the other model classes.
 */
@Singleton
public class Model implements IModel, IObservable, IClipboardManager,
        IFavoriteManager, IFileManager, IMagicBoxManager,
        ISelectionManager, ISourceHandler {

    private final PropertyChangeSupport observers = new PropertyChangeSupport(this);
    private IFileSourceManager fileSourceManager;
    private IFileHandler fileHandler;
    private MagicBoxManager magicBoxManager;
    private IFileSourceProvider fileSourceProvider;

    private Model() {}

    @Inject
    public Model(IFileSourceManager fileSourceManager, IFileHandler fileHandler,
            MagicBoxManager magicBoxManager, IFileSourceProvider fileSourceProvider) {
        this.fileHandler = fileHandler;
        this.fileSourceManager = fileSourceManager;
        this.magicBoxManager = magicBoxManager;
        this.fileSourceProvider = fileSourceProvider;
    }

    //IModel interface --------------------------------------------------------
    /**
     * Initializes model and reportes state to listeners
     */
    public void init() {
        newSource(SourceType.COMPUTER);
        observers.firePropertyChange(UpdateEvent.FAVORITES_CHANGE.toString(),
                null,
                FavoriteSource.getInstance());
        observers.firePropertyChange(UpdateEvent.DEVICES_CHANGE.toString(),
                null,
                fileSourceProvider.create(SourceType.COMPUTER));
        observers.firePropertyChange(UpdateEvent.MAGIC_BOX_CHANGE.toString(),
                null,
                magicBoxManager.getCurrentPlugin());
    }

    /**
     * Prepared shutdown method, if we want to save something before shutdown.
     */
    public void quit() {
        System.exit(0);
    }

    //IClipboardManager interface ---------------------------------------------
    /**
     * Copies the current selection to the system clipboard.
     */
    @Override
    public void copy() {
        try {
            fileHandler.copy(getSelectedFiles());
        } catch (IllegalStateException ex) {
            errorMessage("Clipboard is currently unavailable");
        }
    }

    /**
     * Cuts the selection to the system clipboard.
     */
    @Override
    public void cut() {
        try {
            fileHandler.cut(getSelectedFiles());
        } catch (IllegalStateException ex) {
            errorMessage("Clipboard is currently unavailable");
        }
    }

    /**
     * Pastes the systems clipboard to the current active source.
     */
    @Override
    public void paste() {
        try {
            List<File> fileList = fileHandler.getClipboardContent();
            if (fileHandler.lastActionWasCopy()) {
                if (fileSourceManager.getCurrentSource().addFiles(fileList, TransferHandler.COPY)) {
                    updateSource();
                }
            } else if (!fileHandler.lastActionWasCopy()) {
                if (fileSourceManager.getCurrentSource().addFiles(fileList, TransferHandler.MOVE)) {
                    updateSource();
                }
            }
        } catch (IllegalStateException ex) {
            errorMessage("Clipboard is currently unavailable");
        } catch (UnsupportedFlavorException ex) {
            errorMessage("This type of dataflvaor is unsupported");
        } catch (IOException ex) {
            errorMessage(ex.getMessage());
        }
    }

    //IFileManager interface --------------------------------------------------
    /**
     * Opens the selected things in the current source. If replace is active,
     * any selected folder will replace the current tab.
     * @param replace If a selected folder will replace the current source.
     */
    @Override
    public void open(boolean replace) {
        ArrayList<File> selectedFiles = new ArrayList();
        ArrayList<FileItem> selectedFolders = new ArrayList();

        for (FileItem f : fileSourceManager.getFileItems()) {
            if (f.isSelected() && f.isDirectory()) {
                selectedFolders.add(f);
            } else if (f.isSelected()) {
                selectedFiles.add(f.getFile());
            }
        }

        if (selectedFolders.size() > 1) {
            for (FileItem f : selectedFolders) {
                newSource(f.getAbsolutePath(), false);
            }
        } else if (selectedFolders.size() == 1) {
            newSource(selectedFolders.get(0).getFile().getAbsolutePath(), replace);
        }

        try {
            fileHandler.run(selectedFiles);
        } catch (IllegalArgumentException ex) {
            errorMessage("File doesn't  exist");
        } catch (SecurityException ex) {
            errorMessage("No access to the file");
        } catch (IOException ex) {
            errorMessage("Associated application failed to launch");
        }
    }

    /**
     * Runs the file given by the absolute path
     * @param The absolute file path
     */
    @Override
    public void runFile(String path) {
        List<File> list = new ArrayList<File>();
        list.add(new File(path));
        try {
            fileHandler.run(list);
        } catch (IllegalArgumentException ex) {
            errorMessage("File doesn't  exist");
        } catch (SecurityException ex) {
            errorMessage("No access to the file");
        } catch (IOException ex) {
            errorMessage("Associated application failed to launch");
        }
    }

    /**
     * Deletes the current selection.
     */
    @Override
    public void delete() {
        try {
            fileHandler.delete(getSelectedFiles());
        } catch (SecurityException e) {
            errorMessage("Can´t delete the file because system has no access to the file");
        } finally {
            updateSource();
        }
    }

    /**
     * Renames the file, given by currentPath, to the new path
     * @param currentPath The absolute file path of the file being renamed
     * @param newPath The new absolute file path
     */
    @Override
    public void rename(String currentPath, String newPath) {
        try {
            fileHandler.rename(new File(currentPath), new File(newPath));
        } catch (SecurityException e) {
            errorMessage("Can´t rename the file because system has no access to the file.");
        }
        fileSourceManager.getCurrentSource().updateContent();
        observers.firePropertyChange(UpdateEvent.CONTENT_CHANGE.toString(), null, fileSourceManager.getCurrentSource());
    }

    /**
     * Creates a new folder
     */
    @Override
    public void createFolder() {
        if (fileSourceManager.getCurrentSource().canAdd()
                && fileSourceManager.getCurrentSource().getSourceType().equals(SourceType.FOLDER)) {
            try {
                fileHandler.createFolder((fileSourceManager.getCurrentSource()).getAbsolutePath());
            } catch (SecurityException e) {
                errorMessage(e.getMessage());
            }
        }
        updateSource();
    }

    /**
     * Add files to the current source (Drag 'n Drop)
     */
    @Override
    public void addFiles(List<File> fileList, int action) {
        IFileSource current = fileSourceManager.getCurrentSource();
        try {
            if (current.addFiles(fileList, action)) {
                observers.firePropertyChange(UpdateEvent.CONTENT_CHANGE.toString(), null, current);
            }
        } catch (IOException ex) {
            errorMessage(ex.getMessage());
        }
    }

    /**
     * Adds files to the source (tab) given by tabIndex (Drag 'n Drop)
     */
    @Override
    public void addFilesInTab(List<File> fileList, int tabIndex, int action) {
        IFileSource source = fileSourceManager.getSource(tabIndex);
        try {
            if (source.addFiles(fileList, action)) {
                observers.firePropertyChange(UpdateEvent.CONTENT_CHANGE.toString(), null, source);
            }
        } catch (IOException ex) {
            errorMessage(ex.getMessage());
        }
    }

    /**
     * Adds files to a directory (Drag 'n drop)
     */
    @Override
    public void addFilesInDirectory(List<File> fileList, String path, int action) {
        try {
            if (fileHandler.addFiles(fileList, new File(path), action)) {
                observers.firePropertyChange(UpdateEvent.CONTENT_CHANGE.toString(), null, fileSourceManager.getCurrentSource());
            }
        } catch (SecurityException ex) {
            errorMessage("The files could not be moved, no access to the file ");
        } catch (NullPointerException ex) {
            errorMessage("The files could not be moved, no referens to the file");
        } catch (IOException ex) {
            errorMessage(ex.getMessage());
        }
    }

    /**
     * Searches for a files matching the search string
     * @param text The search string
     */
    @Override
    public void search(String text) {
        IDisplayableFileSource searchResult = fileSourceManager.searchInCurrentSource(text);
        observers.firePropertyChange(UpdateEvent.SOURCE_CHANGE.toString(), null, searchResult);
    }

    //ISelectionManager interface ---------------------------------------------
    /**
     * Selectes or deselects the file given by path, depending on current state
     */
    @Override
    public void setSelected(String path, boolean b) {
        fileSourceManager.getCurrentSource().setSelected(path, b);
        observers.firePropertyChange(UpdateEvent.CONTENT_CHANGE.toString(), null, fileSourceManager.getCurrentSource());
    }

    /**
     * Selects the file given by path and deselects all other files
     */
    @Override
    public void setSingleSelected(String path) {
        fileSourceManager.getCurrentSource().setSingleSelected(path);
        observers.firePropertyChange(UpdateEvent.CONTENT_CHANGE.toString(), null, fileSourceManager.getCurrentSource());
    }

    /**
     * Selects or deselects all files in current source
     */
    @Override
    public void setAllSelected(boolean b) {
        fileSourceManager.getCurrentSource().setAllSelected(b);
        observers.firePropertyChange(UpdateEvent.CONTENT_CHANGE.toString(), null, fileSourceManager.getCurrentSource());
    }

    /**
     * Returns the selected files in current source
     */
    @Override
    public List<File> getSelectedFiles() {
        List<File> files = new ArrayList<File>();
        ArrayList<FileItem> fileItems = fileSourceManager.getFileItems();
        for (FileItem f : fileItems) {
            if (f.isSelected()) {
                files.add(f.getFile());
            }
        }
        return files;
    }

    //ISourceHandler interface ------------------------------------------------
    /**
     * Creates a new source
     */
    @Override
    public void newSource(SourceType s) {
        IDisplayableFileSource source = fileSourceManager.newSource(s);
        observers.firePropertyChange(UpdateEvent.SOURCE_CHANGE.toString(), null, source);
    }

    /**
     * Creates a new folder source
     */
    @Override
    public void newSource(String path, boolean replace) {
        IDisplayableFileSource oldSource = fileSourceManager.getCurrentSource();
        try {
            IDisplayableFileSource newSource = fileSourceManager.newSource(path, replace);
            if (replace) {
                observers.firePropertyChange(UpdateEvent.SOURCE_CHANGE.toString(),
                        oldSource, newSource);
                observers.firePropertyChange(UpdateEvent.CURRENT_SOURCE_CHANGE.toString(),
                        null, fileSourceManager.getCurrentSource());
            } else {
                observers.firePropertyChange(UpdateEvent.SOURCE_CHANGE.toString(),
                        null, newSource);
            }
        } catch (NullPointerException e) {
            errorMessage("This source could not be created.");
        }
    }

    /**
     * Replaces current source with a new one
     */
    @Override
    public void replaceCurrentSource(SourceType source, String path) {
        IDisplayableFileSource oldSource = fileSourceManager.getCurrentSource();
        switch (source) {
            case COMPUTER:
                fileSourceManager.switchCurrentSource(SourceType.COMPUTER, true);
                observers.firePropertyChange(UpdateEvent.SOURCE_CHANGE.toString(), oldSource, fileSourceManager.getCurrentSource());
                break;
            case FOLDER:
                fileSourceManager.newSource(path, true);
                observers.firePropertyChange(UpdateEvent.SOURCE_CHANGE.toString(), oldSource, fileSourceManager.getCurrentSource());
                break;
        }
        observers.firePropertyChange(UpdateEvent.CURRENT_SOURCE_CHANGE.toString(), null, fileSourceManager.getCurrentSource());
    }

    /**
     * Switches current source to the source given by a index
     */
    @Override
    public void switchCurrentSource(int index) {
        fileSourceManager.switchCurrentSourceIndex(index);
        observers.firePropertyChange(UpdateEvent.CURRENT_SOURCE_CHANGE.toString(), null, fileSourceManager.getCurrentSource());
    }

    /**
     * Updates current source
     */
    @Override
    public void updateSource() {
        fileSourceManager.getCurrentSource().updateContent();
        observers.firePropertyChange(UpdateEvent.CONTENT_CHANGE.toString(), null, fileSourceManager.getCurrentSource());
    }

    /**
     * Removes the source given by a index
     */
    @Override
    public void removeSource(int index) {
        if (fileSourceManager.removeSource(index)) {
            observers.firePropertyChange(UpdateEvent.SOURCE_CHANGE.toString(), index, null);
            observers.firePropertyChange(UpdateEvent.CURRENT_SOURCE_CHANGE.toString(), null, fileSourceManager.getCurrentSource());
        }
    }

    /**
     * Removes current source (if it's not the last one)
     */
    @Override
    public void removeCurrentSource() {
        removeSource(fileSourceManager.getCurrentSourceIndex());
    }

    //IFavoriteManager interface ----------------------------------------------
    /**
     * Adds files to favorites
     */
    @Override
    public void addFavorites(List<File> fileList) {
        try {
            if (FavoriteSource.getInstance().addFiles(fileList, TransferHandler.NONE)) {
                observers.firePropertyChange(UpdateEvent.FAVORITES_CHANGE.toString(), null, FavoriteSource.getInstance());
            }
        } catch (IOException ex) {
            errorMessage("Favorite could not write to file, you may lose your favorites when you close the application.");
        }
    }

    /**
     * Removes the file given by path from favorites
     */
    @Override
    public void removeFavorite(String path) {
        FavoriteSource.getInstance().removeFavorite(path);
        observers.firePropertyChange(UpdateEvent.FAVORITES_CHANGE.toString(), null, FavoriteSource.getInstance());
    }

    //IMagicBoxManager interface ----------------------------------------------
    /**
     * Sets a new plugin for the magic box
     */
    @Override
    public void setNewPlugin(File f) {
        try {
            magicBoxManager.loadPlugin(f);
            observers.firePropertyChange(UpdateEvent.MAGIC_BOX_CHANGE.toString(), null, magicBoxManager.getCurrentPlugin());
        } catch (InvalidPluginException e) {
            errorMessage(f.getName() + " could not be loaded, it may not follow the right interface.");
        }
    }

    /**
     * Executes magic box's current plugin
     */
    @Override
    public void executeMagicBox() {
        if (!magicBoxManager.getFiles().isEmpty()) {
            magicBoxManager.setToolbox(getToolbox());
            magicBoxManager.run();
            observers.firePropertyChange(UpdateEvent.MAGIC_BOX_CHANGE.toString(), null, magicBoxManager);
            updateSource();
        }
    }

    /**
     * Moves the files in the magic box to the current source
     * NOTE! The files will be moved from their origin
     */
    @Override
    public void exportMagicBox() {
        if (!magicBoxManager.getFileItems().isEmpty()) {
            if (fileSourceManager.getCurrentSource().canAdd()) {
                List<File> filesToAdd = new ArrayList();
                for (FileItem f : magicBoxManager.getFileItems()) {
                    filesToAdd.add(f.getFile());
                }
                try {
                    if (fileSourceManager.getCurrentSource().addFiles(filesToAdd, TransferHandler.MOVE)) {
                        updateSource();
                        clearMagicBox();
                    } else {
                        errorMessage("Can't add files to this tab.");
                    }
                } catch (IOException ex) {
                    errorMessage(ex.getMessage());
                }

            } else {
                errorMessage("Can't add files to this tab");
            }
        }
    }

    /**
     * Adds files to the magic box (Drag 'n Drop)
     * The files are not moved from their origin
     */
    @Override
    public void addFilesToMagicBox(List<File> fileList) {
        if (magicBoxManager.addFiles(fileList, TransferHandler.NONE)) {
            observers.firePropertyChange(UpdateEvent.MAGIC_BOX_CHANGE.toString(), null, magicBoxManager);
        }
    }

    /**
     * Adds selected files in current source to the magic box
     * The files are not moved from their origin
     */
    @Override
    public void importMagicBox() {
        if (!getSelectedFiles().isEmpty()) {
            List<File> filesToAdd = new ArrayList();
            for (FileItem f : fileSourceManager.getCurrentSource().getFileItems()) {
                if (f.isSelected()) {
                    filesToAdd.add(f.getFile());
                }
            }
            magicBoxManager.addFiles(filesToAdd, TransferHandler.COPY);
            observers.firePropertyChange(UpdateEvent.MAGIC_BOX_CHANGE.toString(), null, magicBoxManager);
        }
    }

    /**
     * Removes all files from magic box (the files stays at their origin)
     */
    @Override
    public void clearMagicBox() {
        if (magicBoxManager.getFileItems().size() != 0) {
            magicBoxManager.clear();
            observers.firePropertyChange(UpdateEvent.MAGIC_BOX_CHANGE.toString(), null, magicBoxManager);
        }
    }

    /**
     * Removes the file given by path from the magic box
     */
    @Override
    public void removeFromMagicBox(String path) {
        magicBoxManager.remove(path);
        observers.firePropertyChange(UpdateEvent.MAGIC_BOX_CHANGE.toString(), null, magicBoxManager);
    }

    //IObservable interface ---------------
    @Override
    public void addPropertyChangeListener(PropertyChangeListener observer) {
        observers.addPropertyChangeListener(observer);
    }

    @Override
    public void removePropertyChangeListener(PropertyChangeListener observer) {
        observers.removePropertyChangeListener(observer);
    }

    //Private methods ----------------
    /**
     * Reportes errors to listeners
     */
    private void errorMessage(String message) {
        observers.firePropertyChange(UpdateEvent.ERROR_OCCURRENCE.toString(), null, message);
    }

    /**
     * Returns a toolbox (for the magic box)
     */
    private IToolbox getToolbox() {
        return new IToolbox() {

            public List<File> getFiles() {
                List<File> list = new ArrayList();
                for (FileItem f : magicBoxManager.getFileItems()) {
                    list.add(new File(f.getAbsolutePath()));
                }
                return list;
            }

            public boolean isCurrentTabFolder() {
                return fileSourceManager.getCurrentSource().
                        getSourceType().equals(SourceType.FOLDER);

            }

            public File getDirectory() {
                return new File(fileSourceManager.getCurrentSource().getAbsolutePath());
            }

            public void clearMagicBox() {
                magicBoxManager.clear();
            }
        };
    }

}
